package com.gateway.springboot.starter.gateway;

import com.gateway.common.config.GatewayConfig;
import com.gateway.plugin.api.GatewayPlugin;
import com.gateway.plugin.api.RemoteAddressResolver;
import com.gateway.plugin.base.cache.CommonApiDataSubscriber;
import com.gateway.plugin.base.cache.CommonPluginDataSubscriber;
import com.gateway.router.base.handler.RouteFilterDataHandler;
import com.gateway.router.base.handler.RoutePredicateDataHandler;
import com.gateway.springboot.starter.gateway.route.CommonRouteDataSubscriber;
import com.gateway.plugin.base.convert.ApiDataConvert;
import com.gateway.plugin.base.convert.PluginDataConvert;
import com.gateway.plugin.base.convert.RouteDataConvert;
import com.gateway.plugin.base.handler.ApiDataHandler;
import com.gateway.plugin.base.handler.PluginDataHandler;
import com.gateway.router.base.handler.RouteDataHandler;
import com.gateway.plugin.base.trie.GatewayTrie;
import com.gateway.plugin.base.trie.GatewayTrieRuleListener;
import com.gateway.springboot.starter.gateway.filter.GatewayPluginGlobalFilter;
import com.gateway.sync.data.api.ApiDataSubscriber;
import com.gateway.sync.data.api.PluginDataSubscriber;
import com.gateway.sync.data.api.RouteDataSubscriber;
import com.gateway.web.configuration.ErrorHandlerConfiguration;
import com.gateway.web.configuration.SpringExtConfiguration;
import com.gateway.web.filter.ApiPathConvertFilter;
import com.gateway.web.filter.CrossFilter;
import com.gateway.web.filter.ExcludeFilter;
import com.gateway.web.filter.FallbackFilter;
import com.gateway.web.filter.FileSizeFilter;
import com.gateway.web.filter.HealthFilter;
import com.gateway.web.forward.ForwardedRemoteAddressResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.WebFilter;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @program: api-gateway
 * @description:
 * @author: YuKai Fan
 * @create: 2025/2/6 09:37
 **/
@Configuration
@ComponentScan("com.gateway")
@AutoConfigureBefore(value = SpringExtConfiguration.class)
@Import(value = ErrorHandlerConfiguration.class)
public class GatewayConfiguration {

    private static final Logger log = LoggerFactory.getLogger(GatewayConfiguration.class);

    /**
     * Init GatewayPluginGlobalFilter.
     *
     * @param plugins this plugins is All impl GatewayPlugin.
     * @param config the config
     * @return {@linkplain GlobalFilter}
     */
    @Bean
    public GlobalFilter gatewayPluginGlobalFilter(final ObjectProvider<List<GatewayPlugin>> plugins, final GatewayConfig config) {
        List<GatewayPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
        List<GatewayPlugin> gatewayPlugins = pluginList.stream()
                .sorted(Comparator.comparingInt(GatewayPlugin::getOrder)).collect(Collectors.toList());
        gatewayPlugins.forEach(gatewayPlugin -> log.info("load plugin:[{}] [{}]", gatewayPlugin.named(), gatewayPlugin.getClass().getName()));
        return new GatewayPluginGlobalFilter(gatewayPlugins, config);
    }

//
//    /**
//     * Init GatewayWebHandler.
//     *
//     * @param plugins this plugins is All impl GatewayPlugin.
//     * @param config the config
//     * @return {@linkplain GatewayWebHandler}
//     */
//    @Bean("webHandler")
//    public GatewayWebHandler gatewayWebHandler(final ObjectProvider<List<GatewayPlugin>> plugins, final GatewayConfig config) {
//        List<GatewayPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
//        List<GatewayPlugin> gatewayPlugins = pluginList.stream()
//                .sorted(Comparator.comparingInt(GatewayPlugin::getOrder)).collect(Collectors.toList());
//        gatewayPlugins.forEach(gatewayPlugin -> log.info("load plugin:[{}] [{}]", gatewayPlugin.named(), gatewayPlugin.getClass().getName()));
//        return new GatewayWebHandler(gatewayPlugins, config);
//    }

    /**
     * common plugin data subscriber.
     *
     * @param pluginDataHandlerList the plugin data handler list
     * @param eventPublisher event publisher
     * @return the plugin data subscriber
     */
    @Bean
    @DependsOn("applicationContextAware")
    public PluginDataSubscriber pluginDataSubscriber(
            final ObjectProvider<PluginDataConvert> function,
            final ObjectProvider<List<PluginDataHandler>> pluginDataHandlerList,
                                                     final ObjectProvider<ApplicationEventPublisher> eventPublisher) {
        CommonPluginDataSubscriber pluginDataSubscriber = new CommonPluginDataSubscriber(
                function.getIfAvailable(),
                pluginDataHandlerList.getIfAvailable(Collections::emptyList),
                eventPublisher.getIfAvailable()
        );
        pluginDataSubscriber.refresh();
        return pluginDataSubscriber;
    }

    /**
     * common api data subscriber.
     *
     * @param function the api data handler list
     * @param apiDataHandlerList event publisher
     */
    @Bean
    public ApiDataSubscriber apiDataSubscriber(
            final ObjectProvider<ApiDataConvert> function,
            final ObjectProvider<List<ApiDataHandler>> apiDataHandlerList) {
        CommonApiDataSubscriber apiDataSubscriber = new CommonApiDataSubscriber(
                function.getIfAvailable(),
                apiDataHandlerList.getIfAvailable(Collections::emptyList)
        );
        apiDataSubscriber.refresh();
        return apiDataSubscriber;
    }


    /**
     * common route data subscriber.
     *
     * @param function the route data handler list
     * @param routeDataHandlerList event publisher
     */
    @Bean
    public RouteDataSubscriber routeDataSubscriber(
            final ObjectProvider<RouteDataConvert> function,
            final ObjectProvider<List<RouteDataHandler>> routeDataHandlerList,
            final ObjectProvider<List<RouteFilterDataHandler>> routeFilterDataHandlerList,
            final ObjectProvider<List<RoutePredicateDataHandler>> routePredicateDataHandlerList) {
        return new CommonRouteDataSubscriber(
                function.getIfAvailable(),
                routeDataHandlerList.getIfAvailable(Collections::emptyList),
                routeFilterDataHandlerList.getIfAvailable(Collections::emptyList),
                routePredicateDataHandlerList.getIfAvailable(Collections::emptyList)
        );
    }

    /**
     * Remote address resolver.
     *
     * @return the remote address resolver
     */
    @Bean
    @ConditionalOnMissingBean(RemoteAddressResolver.class)
    public RemoteAddressResolver remoteAddressResolver() {
        return new ForwardedRemoteAddressResolver(1);
    }

    /**
     * Cross filter.
     * if you application has cross-domain.
     * this is demo.
     * 1. Customize webflux's cross-domain requests.
     * 2. Spring bean Sort is greater than -1.
     *
     * @param gatewayConfig the gateway config
     * @return the web filter
     */
    @Bean
    @Order(Integer.MIN_VALUE + 100)
    @ConditionalOnProperty(name = "gateway.cross.enabled", havingValue = "true")
    public WebFilter crossFilter(final GatewayConfig gatewayConfig) {
        return new CrossFilter(gatewayConfig.getCross());
    }

    /**
     * Body web filter.
     *
     * @param gatewayConfig the gateway config
     * @return the web filter
     */
    @Bean
    @Order(Integer.MIN_VALUE + 10)
    @ConditionalOnProperty(name = "gateway.file.enabled", havingValue = "true")
    public WebFilter fileSizeFilter(final GatewayConfig gatewayConfig) {
        return new FileSizeFilter(gatewayConfig.getFile().getMaxSize());
    }

    /**
     * Exclude filter.
     *
     * @param gatewayConfig the gateway config
     * @return the web filter
     */
    @Bean
    @Order(Integer.MIN_VALUE + 99)
    @ConditionalOnProperty(name = "gateway.exclude.enabled", havingValue = "true")
    public WebFilter excludeFilter(final GatewayConfig gatewayConfig) {
        return new ExcludeFilter(gatewayConfig.getExclude().getPaths());
    }

    /**
     * fallback filter.
     *
     * @param gatewayConfig the gateway config
     * @param dispatcherHandler the dispatcher handler
     * @return the fallback web filter
     */
    @Bean
    @Order(Integer.MIN_VALUE + 99)
    @ConditionalOnProperty(name = "gateway.fallback.enabled", havingValue = "true")
    public WebFilter fallbackFilter(final GatewayConfig gatewayConfig, final DispatcherHandler dispatcherHandler) {
        return new FallbackFilter(gatewayConfig.getFallback().getPaths(), dispatcherHandler);
    }

    /**
     * Health filter.
     *
     * @param gatewayConfig the gateway config
     * @return the web filter
     */
    @Bean
    @Order(Integer.MIN_VALUE + 5)
    @ConditionalOnProperty(name = "gateway.health.enabled", havingValue = "true")
    public WebFilter healthFilter(final GatewayConfig gatewayConfig) {
        return new HealthFilter(gatewayConfig.getHealth().getPaths());
    }

    /**
     * api path filter.
     * @return
     */
    @Bean
    @Order(Integer.MIN_VALUE + 200)
    public WebFilter apiPathConvertFilter() {
        return new ApiPathConvertFilter();
    }

    /**
     * gateway config.
     *
     * @return the gateway config
     */
    @Bean
    @ConfigurationProperties(prefix = "gateway")
    public GatewayConfig gatewayConfig() {
        return new GatewayConfig();
    }

    /**
     * gateway trie config.
     *
     * @param gatewayConfig gateway trie config
     * @return GatewayTrie
     */
    @Bean
    public GatewayTrie gatewayTrie(final GatewayConfig gatewayConfig) {
        return new GatewayTrie(gatewayConfig.getTrie().getChildrenSize(), gatewayConfig.getTrie().getPathRuleCacheSize(),
                gatewayConfig.getTrie().getPathVariableSize(), gatewayConfig.getTrie().getMatchMode());
    }

    /**
     * gateway trie listener.
     *
     * @return GatewayTrieRuleListener
     */
    @Bean
    public GatewayTrieRuleListener gatewayTrieRuleListener() {
        return new GatewayTrieRuleListener();
    }
}
